package com.comp6231.replica1.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.sql.Date;
import java.util.Collections;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

import org.omg.CORBA.ORB;

import com.comp6231.DPIS.StationServer;
import com.comp6231.DPIS.StationServerHelper;
import com.comp6231.DPIS.StationServerPOA;
import com.comp6231.replica1.data.CriminalRecord;
import com.comp6231.replica1.data.Log;
import com.comp6231.replica1.data.MissingRecord;
import com.comp6231.replica1.data.Record;
import com.comp6231.replica1.data.Stations;
import com.comp6231.replica1.exception.InvalidRecordStatusException;
import com.comp6231.replica1.exception.LogOperationException;

/**
 * 
 * @author sknarula (narula.sk@gmail.com)
 * @since September 2013
 * 
 *        This is the class which implements the ServerStationInterface. It
 *        provides the functionalities to be performed by the server.
 * 
 */
public class StationServerImplementation extends StationServerPOA {

	/**
	 * Hash table to save the linked list of records
	 */
	private Hashtable<Character, List<Record>> recordsDb = new Hashtable<Character, List<Record>>();

	/**
	 * String to hold the name of the station for this object
	 */
	private String stationName;

	/**
	 * Attribute to keep record count
	 */
	protected volatile AtomicInteger totalRecords = new AtomicInteger(0);
	private static Object lock = new Object();
	private static Object lockTransfer = new Object();

	/**
	 * Default constructor will initialize the
	 * 
	 */
	public StationServerImplementation(String stationName) {
		this.stationName = stationName;
	}

	@Override
	public boolean createCRecord(String badgeId, String firstName,
			String lastName, String crimeDescription, String criminalStatus) {

		String message = "";
		boolean success = false;
		Record cr = null;
		String recordId = "";
		// create a criminal record and store in the hash table
		try {
			cr = new CriminalRecord(firstName, lastName, crimeDescription,
					criminalStatus);

			recordId = cr.getRecordId();
			if (recordId.endsWith("100000")) {
				throw new Exception("No place to add more records");
			}
			List<Record> criminals = recordsDb.get(cr.getLastName()
					.toUpperCase().charAt(0));
			if (criminals == null) {
				criminals = Collections
						.synchronizedList(new LinkedList<Record>());
				recordsDb.put(cr.getLastName().toUpperCase().charAt(0),
						criminals);
			}
			success = criminals.add(cr);

			if (success)
				totalRecords.getAndIncrement();

			message = (success ? "Operation Successful"
					: "Operation Failed: Could not add record to HashTable");
		} catch (InvalidRecordStatusException e) {
			message = "Operation Failed: " + e.getMessage();
		} catch (Exception e) {
			message = "Operation Failed: " + e.toString();
			e.printStackTrace();
		}

		// log the creation of the record
		Log log;
		try {
			log = new Log(this.getStationName() + ".txt",
					"Create Criminal Record for " + lastName + " with ID "
							+ recordId, badgeId, message);
			log.save();
		} catch (LogOperationException e) {
			System.out.println("Log Operation Exception occurred.");
			e.printStackTrace();
		}

		return success;
	}

	@Override
	public boolean createMRecord(String badgeId, String firstName,
			String lastName, String lastKnownAddress, String lastSeenDate,
			String lastSeenPlace, String missingStatus) {

		String message = "";
		boolean success = false;
		Record mr = null;
		String recordId = "";
		// create a criminal record and store in the hash table
		try {
			mr = new MissingRecord(firstName, lastName, lastKnownAddress,
					Date.valueOf(lastSeenDate), lastSeenPlace, missingStatus);
			recordId = mr.getRecordId();
			if (recordId.endsWith("100000")) {
				throw new Exception("No place to add more records");
			}
			List<Record> missing = recordsDb.get(mr.getLastName().toUpperCase()
					.charAt(0));
			if (missing == null) {
				missing = Collections
						.synchronizedList(new LinkedList<Record>());
				recordsDb
						.put(mr.getLastName().toUpperCase().charAt(0), missing);
			}
			success = missing.add(mr);

			if (success)
				totalRecords.getAndIncrement();

			message = (success ? "Operation Successful"
					: "Operation Failed: Could not add record to HashTable");
		} catch (InvalidRecordStatusException e) {
			message = "Operation Failed: " + e.getMessage();
		} catch (Exception e) {
			message = "Operation Failed: " + e.toString();
			e.printStackTrace();
		}

		// log the creation of the record
		Log log;
		try {
			log = new Log(this.getStationName() + ".txt",
					"Create Missing Record for " + lastName + " with ID "
							+ recordId, badgeId, message);
			log.save();
		} catch (LogOperationException e) {
			System.out.println("Log Operation Exception occurred.");
			e.printStackTrace();
		}

		return success;
	}

	@Override
	public String getRecordCounts(String badgeId) {
		DatagramSocket aSocket = null;
		String recordsCount = "";
		String message = "";
		boolean first = true;

		for (Stations station : Stations.values()) {

			if (!first) {
				recordsCount = recordsCount + ", ";
			}
			if (!station.getPoliceStationType().equals(this.getStationName())) {
				try {
					aSocket = new DatagramSocket();
					byte[] m = "GetCount".getBytes();
					InetAddress aHost = InetAddress.getByName("localhost");
					int serverPort = station.getPoliceStationUdpValue();
					DatagramPacket request = new DatagramPacket(m,
							"GetCount".length(), aHost, serverPort);
					aSocket.send(request);
					byte[] buffer = new byte[1000];
					DatagramPacket reply = new DatagramPacket(buffer,
							buffer.length);
					aSocket.receive(reply);

					recordsCount = recordsCount
							+ (station.getPoliceStationType() + ": " + new String(
									reply.getData())).trim();
					first = false;

				} catch (SocketException e) {
					message = "Socket Exception occured: " + e.getMessage();
				} catch (IOException e) {
					message = "IO Exception occured: " + e.getMessage();
				} finally {
					if (aSocket != null)
						aSocket.close();
				}
			} else if (station.getPoliceStationType().equals(
					this.getStationName())) {
				recordsCount = recordsCount + this.getStationName() + ": "
						+ getTotalRecords();
				first = false;
			}
		}
		message = "Count Returned: \"" + recordsCount.trim() + "\"";

		// log the creation of the record
		Log log;
		try {
			log = new Log(this.getStationName() + ".txt",
					"Requested Record Count", badgeId, message);
			log.save();
		} catch (LogOperationException e) {
			System.out.println("Log Operation Exception occurred.");
			e.printStackTrace();
		}

		return recordsCount;
	}

	@Override
	public boolean editRecord(String badgeId, String lastName, String recordId,
			String newStatus) {

		String message = "";
		boolean success = false;
		List<Record> recordList = null;
		// lets first find the record in the list
		try {
			recordList = recordsDb.get(lastName.toUpperCase().charAt(0));
			if (recordList == null) {
				throw new NullPointerException("Record list not found");
			}
			for (Record e : recordList) {
				if (e.getRecordId().equals(recordId)) {
					e.setStatus(newStatus);
					success = true;
					break;
				}
			}
			message = (success ? "Operation Successful"
					: "Operation Failed: Record not found on the list");
		} catch (NullPointerException e) {
			message = "Operation Failed: " + e.getMessage();
		} catch (InvalidRecordStatusException e) {
			message = "Operation Failed: " + e.getMessage();
		}

		// log the creation of the record
		Log log;
		try {
			log = new Log(this.getStationName() + ".txt",
					"Updating Record for " + lastName + " with ID " + recordId,
					badgeId, message);
			log.save();
		} catch (LogOperationException e) {
			System.out.println("Log Operation Exception occurred.");
			e.printStackTrace();
		}

		return success;
	}

	@Override
	public boolean transferRecord(String badgeID, String recordID,
			String remoteStationServerName) {

		String message = "";
		boolean success = false;
		List<Record> recordList = null;

		try {
			// lets first find the record in the list
			for (Entry<Character, List<Record>> entry : recordsDb.entrySet()) {
				recordList = entry.getValue();

				if (recordList != null) {
					for (Record e : recordList) {
						if (e.getRecordId().equals(recordID)) {
							// get the record credentials and create a string
							// array
							String[] record = null;
							if (e.getType().toString().equals("CRIMINAL")) {
								CriminalRecord cr = (CriminalRecord) e;
								record = new String[7];
								record[0] = cr.getRecordId();
								record[1] = cr.getFirstName();
								record[2] = cr.getLastName();
								record[3] = cr.getCrimeDescription();
								record[4] = cr.getStatus().toString();
								record[5] = "";
								record[6] = "";
							} else if (e.getType().toString().equals("MISSING")) {
								MissingRecord mr = (MissingRecord) e;
								record = new String[7];
								record[0] = mr.getRecordId();
								record[1] = mr.getFirstName();
								record[2] = mr.getLastName();
								record[3] = mr.getLastKnownAddress();
								record[4] = mr.getLastSeenDate().toString();
								record[5] = mr.getLastSeenPlace();
								record[6] = mr.getStatus().toString();
							}

							// add the record in remoteServer
							ORB orb = ORB.init(new String[] { "" }, null);

							// get the ior mapping and connect to server
							BufferedReader br = new BufferedReader(
									new FileReader(
											System.getProperty("user.dir")
													+ "/src/com/comp6231/replica1/server/"
													+ remoteStationServerName
													+ "Ior.txt"));
							String ior = br.readLine();
							br.close();

							org.omg.CORBA.Object o = orb.string_to_object(ior);

							StationServer server2 = StationServerHelper
									.narrow(o);

							// to maximize concurrency, upon acquiring the lock,
							// check again to make sure record is still in the
							// list
							// and not transfered by another client call
							synchronized (lockTransfer) {
								if (recordList.contains(e)) {
									server2.transfer(record);

									// remove the record from this list
									recordList.remove(e);

									totalRecords.getAndDecrement();
									success = true;
								}
							}

							break;

						} // end of if record found
					} // end of for - looping over record list for a character
				}
			}
			// log message
			message = (success ? "Operation Successful"
					: "Operation Failed: Record not found on the list");
		} catch (NullPointerException e) {
			message = "Operation Failed: " + e.getMessage();
		} catch (InvalidRecordStatusException e) {
			message = "Operation Failed: " + e.getMessage();
		} catch (IOException e) {
			message = "Operation Failed: " + e.getMessage();
		}

		// log the creation of the record
		Log log;
		try {
			log = new Log(this.getStationName() + ".txt",
					"Transfer Record with ID " + recordID + " to "
							+ remoteStationServerName, badgeID, message);
			log.save();
		} catch (LogOperationException e) {
			System.out.println("Log Operation Exception occurred.");
			e.printStackTrace();
		}
		return success;
	}

	@Override
	public boolean transfer(String[] record) {
		boolean success = false;
		String message = "";
		String recordType = "";
		// check to see if this is criminal or missing record
		try {
			if (record[0].startsWith("CR")) {
				recordType = "Criminal";
				CriminalRecord cr = new CriminalRecord(record[0], record[1],
						record[2], record[3], record[4]);

				List<Record> criminals = recordsDb.get(cr.getLastName()
						.toUpperCase().charAt(0));
				if (criminals == null) {
					criminals = Collections
							.synchronizedList(new LinkedList<Record>());
					recordsDb.put(cr.getLastName().toUpperCase().charAt(0),
							criminals);
				}
				success = criminals.add(cr);
			} else if (record[0].startsWith("MR")) {
				recordType = "Missing";
				MissingRecord mr = new MissingRecord(record[0], record[1],
						record[2], record[3], Date.valueOf(record[4]),
						record[5], record[6]);

				List<Record> missing = recordsDb.get(mr.getLastName()
						.toUpperCase().charAt(0));
				if (missing == null) {
					missing = Collections
							.synchronizedList(new LinkedList<Record>());
					recordsDb.put(mr.getLastName().toUpperCase().charAt(0),
							missing);
				}
				success = missing.add(mr);
			}

			if (success)
				totalRecords.getAndIncrement();

			message = (success ? "Operation Successful"
					: "Operation Failed: Could not add record to HashTable");

		} catch (InvalidRecordStatusException e) {
			message = "Operation Failed: " + e.getMessage();
		} catch (Exception e) {
			message = "Operation Failed: " + e.toString();
			e.printStackTrace();
		}

		// log the creation of the record
		Log log;
		try {
			log = new Log(this.getStationName() + ".txt", "Transfered "
					+ recordType + " Record for " + record[2] + " with ID "
					+ record[0], "From Remote Server", message);
			log.save();
		} catch (LogOperationException e) {
			System.out.println("Log Operation Exception occurred.");
			e.printStackTrace();
		}

		return success;
	}

	@Override
	public void printHashTable() {
		BufferedWriter bout;
		PrintWriter out;
		String newLine = System.getProperty("line.separator");
		synchronized (lock) {
			try {
				bout = new BufferedWriter(new FileWriter(new File(
						"HashTables.txt"), true));
				out = new PrintWriter(bout);
				out.print(newLine);
				out.print(newLine);
				out.print("******" + this.getStationName() + "******");
				out.print(newLine);
				out.print(recordsDb);
				out.print(newLine);
				out.flush();
				out.close();
				bout.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public int getTotalRecords() {
		return totalRecords.get();
	}

	/**
	 * Getters and Setters
	 */

	public String getStationName() {
		return stationName;
	}

	public void setStationName(String stationName) {
		this.stationName = stationName;
	}

}
